package it.eng.area118.mdocommon.android.xml;

import it.eng.area118.mdocommon.android.log.BasicLogger;
import it.eng.area118.mdocommon.android.util.BooleanUtils;
import it.eng.area118.mdocommon.android.util.NumberUtils;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XmlElement {

	protected Element rootElement;

	public XmlElement() throws ParserConfigurationException {
	}

	public XmlElement(File file) throws XmlParseException {
		DocumentBuilderFactory domFactory = DocumentBuilderFactory
				.newInstance();
		domFactory.setNamespaceAware(true);
		try {
			rootElement = domFactory.newDocumentBuilder().parse(file)
					.getDocumentElement();
		} catch (Exception e) {
			throw new XmlParseException("PARSING ERROR", e);
		}
	}

	public XmlElement(InputStream stream) throws XmlParseException {
		DocumentBuilderFactory domFactory = DocumentBuilderFactory
				.newInstance();
		domFactory.setNamespaceAware(true);
		try {
			rootElement = domFactory.newDocumentBuilder().parse(stream)
					.getDocumentElement();
		} catch (Exception e) {
			throw new XmlParseException("PARSING ERROR", e);
		}
	}

	public XmlElement(XmlElement xml, boolean clone) throws XmlParseException {
		this(xml.getRootElement(), clone);
	}

	public XmlElement(Element elem, boolean clone) {
		if (elem == null) {
			throw new XmlParseException("INVALID PARAMETERS");
		}

		if (clone) {
			try {
				DocumentBuilderFactory domFactory = DocumentBuilderFactory
						.newInstance();
				domFactory.setNamespaceAware(true);
				rootElement = (Element) domFactory.newDocumentBuilder()
						.newDocument().importNode(elem, true);
			} catch (Exception ex) {
				throw new XmlParseException("PARSING ERROR", ex);
			}
		} else {
			this.rootElement = elem;
		}
	}

	public XmlElement(byte[] bytes) throws XmlParseException {
		DocumentBuilderFactory domFactory = DocumentBuilderFactory
				.newInstance();
		domFactory.setNamespaceAware(true);
		try {
			rootElement = domFactory.newDocumentBuilder()
					.parse(new ByteArrayInputStream(bytes))
					.getDocumentElement();
		} catch (Exception e) {
			throw new XmlParseException("PARSING ERROR", e);
		}
	}

	public XmlElement(String name) {
		try {
			Document doc = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder().newDocument();
			rootElement = doc.createElement(name);
			doc.appendChild(rootElement);
		} catch (Exception ex) {
		}
	}

	public XmlElement addAttribute(String name, String value) {
		rootElement.setAttribute(name, value);
		return this;
	}

	public XmlElement getParent(boolean clone) {
		XmlElement xmlParent = null;
		if (rootElement != null) {
			Node parent = rootElement.getParentNode();
			if (parent != null && Node.ELEMENT_NODE == parent.getNodeType()) {
				xmlParent = new XmlElement((Element) parent, clone);
			}
		}
		return xmlParent;
	}

	public void addElement(XmlElement elem) {
		addElement(elem, false);
	}

	public void addElement(XmlElement elem, boolean clone) {
		if (elem == null)
			return;
		if (clone) {
			rootElement.appendChild(rootElement.getOwnerDocument().importNode(
					elem.getRootElement(), clone));
		} else {
			Node node = rootElement.getOwnerDocument().adoptNode(
					elem.getRootElement());
			rootElement.appendChild(node);
		}
	}

	public XmlElement addElement(String name, String text) {
		Element elem = rootElement.getOwnerDocument().createElement(name);
		elem.setTextContent(text);
		rootElement.appendChild(elem);
		return new XmlElement(elem, false);
	}

	protected Node getChild(String name) {
		Node child = null;
		NodeList list = rootElement.getElementsByTagName(name);
		if (list != null && list.getLength() > 0) {
			child = list.item(0);
		}
		return child;
	}

	public XmlElement getFirstChild(boolean clone) {
		Element elem = (Element) rootElement.getFirstChild();
		if (elem != null) {
			return new XmlElement(elem, clone);
		}
		return null;
	}

	protected List<XmlElement> getChilds(boolean clone) {
		Vector<XmlElement> childs = new Vector<XmlElement>();
		NodeList list = rootElement.getChildNodes();
		if (list != null && list.getLength() > 0) {
			for (int idx = 0; idx < list.getLength(); idx++) {
				childs.add(new XmlElement((Element) list.item(idx), clone));
			}
		}
		return childs;
	}

	public List<XmlElement> removeChilds(boolean clone) {
		Vector<XmlElement> childs = new Vector<XmlElement>();
		NodeList list = rootElement.getChildNodes();
		if (list != null && list.getLength() > 0) {
			for (int idx = 0; idx < list.getLength(); idx++) {
				Node node = list.item(idx);
				childs.add(new XmlElement((Element) node, clone));
				rootElement.removeChild(node);
			}
		}
		return childs;
	}

	public XmlElement removeElement(String xpath) {
		XmlElement elem = null;
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			Node node = (Node) xPath.evaluate(xpath, rootElement,
					XPathConstants.NODE);
			if (node != null) {
				rootElement.removeChild(node);
				elem = new XmlElement((Element) node, false);
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return elem;
	}

	protected Node removeFirstChild(String name) {
		Node node = null;
		NodeList list = rootElement.getElementsByTagName(name);
		if (list != null && list.getLength() > 0) {
			node = rootElement.removeChild(list.item(0));
		}
		return node;
	}

	public void setElement(String name, String text) {
		Node node = null;
		NodeList list = rootElement.getElementsByTagName(name);
		if (list.getLength() > 0) {
			node = list.item(0);
		} else {
			node = rootElement.getOwnerDocument().createElement(name);
			rootElement.appendChild(node);
		}
		node.setTextContent(text);
	}

	public XmlElement removeElement(XmlElement elem) {
		XmlElement xmlelm = null;
		if (elem != null) {
			String name = elem.getName();

			NodeList list = rootElement.getElementsByTagName(name);
			if (list != null && list.getLength() > 0) {
				Node node = rootElement.removeChild(list.item(0));
				xmlelm = new XmlElement((Element) node, false);
			}
		}
		return xmlelm;
	}

	public String getName() {
		return rootElement.getTagName();
	}

	public void setText(String text) {
		rootElement.setTextContent(text);
	}

	public String getText() {
		return rootElement.getTextContent();
	}

	public int countChilds() {
		return rootElement.getChildNodes().getLength();
	}

	protected void removeText() {
		rootElement.setTextContent("");
	}

	public String toString() {
		try {
			DOMSource domSource = new DOMSource(rootElement);
			StringWriter out = new StringWriter();
			TransformerFactory tf = TransformerFactory.newInstance();
			Transformer serializer = tf.newTransformer();
			serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
			serializer.setOutputProperty(OutputKeys.INDENT, "no");
			serializer
					.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

			serializer.transform(domSource, new StreamResult(out));

			return out.toString();
		} catch (Exception e) {
			BasicLogger.writeLog("ERROR", "Error", e);
			return "";
		}
	}

	protected Element getRootElement() {
		return rootElement;
	}

	public int countNodes(String query) {
		int count = 0;
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			NodeList list = (NodeList) xPath.evaluate(query, rootElement,
					XPathConstants.NODESET);
			if (list != null) {
				count = list.getLength();
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return count;
	}

	public List<XmlElement> selectNodes(String query) {
		return selectNodes(query, true);
	}

	public List<XmlElement> selectNodes(String query, boolean clone) {
		Vector<XmlElement> nodes = new Vector<XmlElement>();
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			NodeList list = (NodeList) xPath.evaluate(query, rootElement,
					XPathConstants.NODESET);
			if (list != null && list.getLength() > 0) {
				for (int idx = 0; idx < list.getLength(); idx++) {
					nodes.add(new XmlElement((Element) list.item(idx), clone));
				}
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return nodes;
	}

	// public List<XmlElement> selectNodes(String query, Class theclass, boolean
	// clone) {
	// Vector nodes = new Vector();
	// if (rootElement != null) {
	// Enumeration enumeration = null;
	// try {
	// enumeration =
	// rootElement.getOwnerDocument().xpathSelectElements(query);
	// while (enumeration.hasMoreElements()) {
	// Element element = (Element) enumeration.nextElement();
	//
	// if (theclass != null) {
	// Object node = null;
	// Constructor constructor = null;
	// try {
	// constructor = theclass.getDeclaredConstructor(new Class[] {
	// com.hp.hpl.sparta.Element.class, boolean.class });
	// } catch (Exception ex) {
	// constructor = null;
	// ex.printStackTrace();
	// }
	// if (constructor != null) {
	// try {
	// node = constructor.newInstance(new Object[] { element, (clone) ?
	// Boolean.TRUE : Boolean.FALSE });
	// } catch (Exception ex1) {
	// node = null;
	// ex1.printStackTrace();
	// }
	// }
	// if (node != null) {
	// nodes.addElement(node);
	// }
	// } else {
	// XmlElement cfg = new XmlElement(element, clone);
	// nodes.addElement(cfg);
	// }
	// }
	// } catch (Exception e) {
	// e.printStackTrace();
	// }
	// }
	// return nodes;
	// return null;
	// }

	// public Object selectNode(String query, Class theclass, boolean clone) {
	// return null;
	// }

	public boolean existsNode(String query) {
		boolean exists = false;
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			NodeList list = (NodeList) xPath.evaluate(query, rootElement,
					XPathConstants.NODESET);
			if (list != null) {
				exists = list.getLength() > 0;
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return exists;
	}

	public XmlElement selectNode(String query, boolean clone) {
		XmlElement elem = null;
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			Node node = (Node) xPath.evaluate(query, rootElement,
					XPathConstants.NODE);
			if (node != null && Node.ELEMENT_NODE == node.getNodeType()) {
				elem = new XmlElement((Element) node, clone);
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return elem;
	}

	public List<String> selectNodesText(String query) {
		Vector<String> texts = new Vector<String>();
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			NodeList list = (NodeList) xPath.evaluate(query, rootElement,
					XPathConstants.NODESET);
			if (list != null && list.getLength() > 0) {
				for (int idx = 0; idx < list.getLength(); idx++) {
					Node node = list.item(idx);
					if (node != null && Node.TEXT_NODE == node.getNodeType()) {
						texts.add(node.getTextContent());
					}
				}
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return texts;
	}

	public String selectNodeText(String query) {
		String text = "";
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			Node node = (Node) xPath.evaluate(query, rootElement,
					XPathConstants.NODE);
			if (node != null && Node.TEXT_NODE == node.getNodeType()) {
				text = node.getTextContent();
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return text;
	}

	public String selectValue(String query, String defaultValue) {
		String value = defaultValue;
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			Node node = (Node) xPath.evaluate(query, rootElement,
					XPathConstants.NODE);
			if (node != null) {
				value = node.getTextContent();
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return value;
	}

	public int selectValue(String query, int defaultValue) {
		int value = defaultValue;
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			Node node = (Node) xPath.evaluate(query, rootElement,
					XPathConstants.NODE);
			if (node != null) {
				value = NumberUtils.stringToInt(node.getTextContent(),
						defaultValue);
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return value;
	}

	public float selectValue(String query, float defaultValue) {
		float value = defaultValue;
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			Node node = (Node) xPath.evaluate(query, rootElement,
					XPathConstants.NODE);
			if (node != null) {
				value = NumberUtils.stringToFloat(node.getTextContent(),
						defaultValue);
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return value;
	}

	public boolean selectValue(String query, boolean defaultValue) {
		boolean value = defaultValue;
		try {
			XPath xPath = XPathFactory.newInstance().newXPath();
			Node node = (Node) xPath.evaluate(query, rootElement,
					XPathConstants.NODE);
			if (node != null) {
				value = BooleanUtils.toBoolean(node.getTextContent());
			}
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return value;
	}

	public String getAttribute(String name) {
		return rootElement.getAttribute(name);
	}

	/**
	 * Classe per il parsing di stream continuati di documenti xml. Non �
	 * vincolato alla struttura xml. Pu� cercare la prima occorrenza di un tag
	 * arbitrario e permette di leggere il contenuto del documento xml dal tag
	 * di inizio fino a quello di chiususra.
	 */
	protected static class XmlInputStream extends BufferedInputStream {
		private boolean xmlStarted;

		private boolean inTag;

		private StringBuffer currentTag;

		private boolean endStream;

		private String tagName;

		private String endTagName;

		private Map attributes;

		private boolean childOnly;

		private int instanceCount;

		public XmlInputStream(InputStream inStream, String theTag) {
			super(inStream);
			currentTag = new StringBuffer();
			tagName = theTag;
			endTagName = '/' + theTag;
			attributes = null;
			childOnly = false;
		}

		public synchronized void reopen(String theTag) {
			endStream = false;
			xmlStarted = false;
			inTag = false;
			tagName = theTag;
			endTagName = '/' + theTag;
			attributes = null;
			childOnly = false;
		}

		public synchronized int read() throws IOException {
			if (!xmlStarted) {
				skipToTag();
				xmlStarted = true;
			}
			if (endStream) {
				return -1;
			}
			int ch = superRead();
			endStream = isLastTag(ch);
			return ch;
		}

		public synchronized int read(byte[] b, int off, int len)
				throws IOException {
			if (!xmlStarted) {
				skipToTag();
				xmlStarted = true;
			}
			if (endStream) {
				return -1;
			}

			int cnt;
			for (cnt = 0; cnt < len; ++cnt) {
				int read = read();
				if (read == -1) {
					break;
				}
				b[cnt + off] = (byte) read;
			}

			if (cnt > 0) {
				return cnt;
			}
			return -1;
		}

		/**
		 * indica se il carattere fa parte del tag di chiusura. Ogni carattere
		 * letto dopo il tag di apertura passa attraverso questo metodo.
		 * 
		 * @param ch
		 *            int
		 * @return boolean
		 */
		private boolean isLastTag(int ch) {
			if (ch == '<') {
				inTag = true;
				currentTag.setLength(0);
			} else if (ch == '>') {
				inTag = false;
				if (currentTag.toString().equals(endTagName)) {
					return true;
				}
			} else if (inTag) {
				currentTag.append((char) ch);
			}
			return false;
		}

		/**
		 * legge lo stream di input finch� non arriva al tag.
		 * 
		 * @throws IOException
		 */
		private void skipToTag() throws IOException {
			char[] searchChars = tagName.toCharArray();
			int i;
			boolean found;
			while (true) {
				if (!childOnly) {
					mark(1000);
				}
				// legge finch� non ottiene <
				while (realRead() != '<') {
					if (!childOnly) {
						mark(1000);
					}
				}
				found = true;
				// Confronta ogni carattere successivo per verificare
				// se appartiene alla stringa del tag desiderato.
				for (i = 0; i < tagName.length(); ++i) {
					if (realRead() != searchChars[i]) {
						found = false;
						break;
					}
				}
				int terminator = realRead();
				// verifica corrispondenza con > o altro carattere
				if (found && !isNameTerminator((char) terminator)) {
					found = false;
				}

				if (found) {
					// tag trovato. inizia la lettura degli attributi
					if (attributes != null) {
						Map attributesFound = new HashMap();
						if (terminator != '>') {
							attributesFound = readAttributes();
						}

						// verifica congruenza attributi, se fallisce il tag
						// non � stato trovato => inizia la ricerca di un
						// nuovo tag
						Iterator it = attributes.entrySet().iterator();
						while (found && it.hasNext()) {
							Map.Entry pair = (Map.Entry) it.next();
							if (!pair.getValue().equals(
									attributesFound.get(pair.getKey()))) {
								found = false;
							}
						}
					}
				}

				if (found) {
					if (instanceCount < 0) {
						found = false;
						++instanceCount;
					}
				}

				if (found) {
					if (childOnly) {
						// legge il nome del tag figlio
						// e poi fa un reset della posizione
						// di lettura a tale tag
						mark(1000);
						while (realRead() != '<') {
						}
						tagName = "";
						char ch = (char) realRead();
						while (!isNameTerminator(ch)) {
							tagName += ch;
							ch = (char) realRead();
						}
						endTagName = "/" + tagName;
						System.out.println("Start tag = " + tagName);
						System.out.println("End tag = " + endTagName);
					}
					reset();
					return;
				}
			}
		}

		private boolean isNameTerminator(char ch) {
			return (ch == '>' || Character.isWhitespace(ch));
		}

		/**
		 * lettura attributi
		 * 
		 * @return Map
		 * @throws IOException
		 */
		private Map<String, String> readAttributes() throws IOException {
			Map<String, String> attributesFound = new HashMap<String, String>();
			int character;
			while ((character = realRead()) != '>') {
				if (!Character.isWhitespace((char) character)) {
					StringBuffer attributeName = new StringBuffer();
					attributeName.append((char) character);
					while ((character = realRead()) != '='
							&& !Character.isWhitespace((char) character)) {
						attributeName.append((char) character);
					}
					// salta tutti gli spazi
					while (Character.isWhitespace((char) character)) {
						character = realRead();
					}
					if (character != '=') {
						throw new IOException("Missing = sign after attribute "
								+ attributeName);
					}
					// salta tutti gli spazi
					int quoteSymbol = realRead();
					while (Character.isWhitespace((char) quoteSymbol)) {
						quoteSymbol = realRead();
					}
					if (quoteSymbol != '"' && quoteSymbol != '\'') {
						throw new IOException(
								"Missing \" or ' around attribute value after attribute "
										+ attributeName);
					}
					StringBuffer attributeValue = new StringBuffer();
					while ((character = realRead()) != quoteSymbol) {
						attributeValue.append((char) character);
					}
					attributesFound.put(attributeName.toString(),
							attributeValue.toString());
				}
			}
			return attributesFound;
		}

		public void close() throws IOException {
		}

		public void realClose() throws IOException {
			super.close();
		}

		private int realRead() throws IOException {
			int read = superRead();
			if (read == -1) {
				throw new IOException("Tag " + tagName + " not found");
			}
			return read;
		}

		private int superRead() throws IOException {
			return super.read();
		}
	}

	public static XmlElement parseStream(InputStream iStream, String rootTag)
			throws XmlParseException {
		XmlElement xmlElement = null;
		try {
			XmlInputStream xmlstream = new XmlInputStream(iStream, rootTag);
			int readed = 0;
			StringBuffer xml = new StringBuffer();
			while (readed != -1) {
				readed = xmlstream.read();
				if (readed != -1) {
					xml.append((char) readed);
				}
			}
			xmlElement = new XmlElement(xml.toString().getBytes());
		} catch (IOException ex) {
			throw new XmlParseException("stream read failed : " + ex.toString());
		}
		return xmlElement;
	}
}